package com.cy.pj.sys.service.impl;

import java.util.HashMap;

import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import com.cy.pj.common.annotation.RequiredLog;
import com.cy.pj.common.exception.ServiceException;
import com.cy.pj.common.pojo.PageObject;
import com.cy.pj.common.util.ShiroUtli;
import com.cy.pj.sys.dao.SysUserDao;
import com.cy.pj.sys.dao.SysUserRoleDao;
import com.cy.pj.sys.pojo.SysUser;
import com.cy.pj.sys.pojo.SysUserDept;
import com.cy.pj.sys.service.SysUserService;

/**
 * @Transactional 此注解为事务控制，可以加在类上也可以加在方法上
 * 如果加在类上，表示类中所有方法都会进行事务控制
 * 加在方法上，表示只有此方法会进行事务控制
 *
 */
@Transactional(readOnly = false,//指定事务是否为只读事务，比如查询，可以设置为ture，只进行读操作，添加修改删除业务则需要设置为false，因为它们还执行了写
			   rollbackFor = RuntimeException.class,//表示只要抛出了这个类型的异常就会回滚
			   noRollbackFor = ServiceException.class,//表示抛出这个类型的异常时不会回滚
			   timeout = -1,//表示超时时间
			   isolation = Isolation.READ_COMMITTED	//表示事务的隔壁级别
			  ) 
@Service
public class SysUserServiceImpl implements SysUserService {

	@Autowired
	private SysUserDao sysUserDao;
	
	@Autowired
	private SysUserRoleDao sysUserRoleDao;
	
	//查询所有的角色信息
	@Transactional(readOnly = true)
	@RequiredLog("分页查询")
	@Override
	public PageObject<SysUserDept> findObjects(String username, Integer pageCurrent) {
		//校验基本参数
		if(pageCurrent==null||pageCurrent<1)
			throw new IllegalArgumentException("页码值无效");
		//查询总记录数
		int rowCount = sysUserDao.getRowCount(username);
		if(rowCount==0)
			throw new IllegalArgumentException("系统已经没有角色啦");
		int pageSize=3;//定义一次查询几条记录
		int startIndex=(pageCurrent-1)*pageSize;//定义查询开始位置
		//根据用户名以及开始位置和最大查询记录   进行查询，封装到List集合中
		List<SysUserDept> records = sysUserDao.findPageObjects(username, startIndex, pageSize);
		//返回给调用者
		return new PageObject<>(pageCurrent, pageSize, rowCount, records);
	}


	/**
	  * @RequiresPermissions 这个注解描述的方法为一个切入点方法。此方法在执行之前
	  *    需要进行权限检测(负责这个过程的方法是一个通知方法)，假如用户权限中包含
	  * @RequiresPermissions 注解value属性指定的值，则授权访问，不包含则抛出异常。
	  *   思考：假如你去设计这个切入点对应的通知方法，你会做什么？
	  *1)目标方法执行执行获取方法上的@RequiresPermissions注解，进而取到注解中内容。
	  *2)将注解中内容提交给SecurityManager对象(此对象负责授权操作)
	  *3)SecurityManager会基于realm去查找用户拥有的权限(这部分我们自己实现)。
	  *4)SecurityManager会判断用户拥有权限中是否包含RequiresPermissions注解中的内容
	  *5)SecurityManager基于用户权限进行授权或抛出异常。
	 */
	//修改用户状态信息
	@RequiresPermissions("sys:user:update")
	@RequiredLog("用户修改状态")
	@Override
	public int validById(Integer id, Integer valid) {
		//校验基本参数
		if(id==null||id<1)
			throw new IllegalArgumentException("Id不合法");
		if(valid==null||(valid!=1&&valid!=0))
			throw new IllegalArgumentException("参数不合法，请刷新页面重试");
		//修改用户状态信息
		int rows = sysUserDao.valiById(id, valid, "admin");
		if(rows==0)
			throw new IllegalArgumentException("此记录可能已经被删除了");
		return rows;
	}


	//新建角色
	@RequiredLog("新增角色")
	@Override
	public int saveObject(SysUser entity, Integer[] roleIds) {
		//基本参数校验
		if(entity==null)
			throw new IllegalArgumentException("保存对象不能我空");
		if(StringUtils.isEmpty(entity.getUsername()))
			throw new IllegalArgumentException("用户名不能为空");
		if(StringUtils.isEmpty(entity.getPassword()))
			throw new IllegalArgumentException("密码不能为空");
		if(roleIds==null||roleIds.length==0)
			throw new IllegalArgumentException("请至少为用户分配一个角色");
		String source = entity.getPassword();//原密码
		String salt = UUID.randomUUID().toString();//随机字符串作为加密盐
		SimpleHash sh = new SimpleHash("MD5",source,salt,1);//加密方式为MD5,原密码，盐值，加密次数
		entity.setSalt(salt);
		entity.setPassword(sh.toHex());//将加密结果转换为16进制
		//保存用户信息
		int rows = sysUserDao.insertObject(entity);
		//保存用户角色关系数据
		sysUserRoleDao.insertObjects(entity.getId(), roleIds);
		return rows;
	}


	//点击更新时，查询选中的角色信息返回到前端
	@Override
	public Map<String, Object> findObjectById(Integer userId) {
		//基本参数校验
		if(userId==null||userId<1)
			throw new IllegalArgumentException("参数值不合法，请刷新页面后重试");
		//基于用户id查询用户以及用户对应的部门信息
		SysUserDept user = sysUserDao.findObjectById(userId);
		if(user==null)
			throw new IllegalArgumentException("用户可能已经被删除");
		//基于用户id查询用户对应的角色信息
		List<Integer> roleIds = sysUserRoleDao.findRoleIdsByUserId(userId);
		//封装结果返回给调用者
		Map<String,Object> map = new HashMap<>();
		map.put("user", user);
		map.put("roleIds", roleIds);
		return map;
	}


	@RequiredLog("修改用户信息")
	@Override
	public int updateObject(SysUser entity, Integer[] roleIds) {
		//参数校验
		if(entity==null)
			throw new IllegalArgumentException("保存对象不能为空");
		if(StringUtils.isEmpty(entity.getUsername()))
			throw new IllegalArgumentException("用户名不能为空");
		if(roleIds==null||roleIds.length==0)
			throw new IllegalArgumentException("请至少为其分配一个角色");
		//2.更新用户自身信息
		int rows = sysUserDao.updateObject(entity);
		//3.保存用户角色关系数据
		sysUserRoleDao.deleteObjectByUserId(entity.getId());
		sysUserRoleDao.insertObjects(entity.getId(),roleIds);
		return rows;
	}


	//修改密码
	@RequiredLog("修改密码")
	@Override
	public int updatePassword(String sourcePassword, String newPassword, String cfgPassword) {
		//1.参数校验
		if(StringUtils.isEmpty(sourcePassword))
			throw new IllegalArgumentException("原密码不能为空");
		if(StringUtils.isEmpty(newPassword))
			throw new IllegalArgumentException("新密码不能为空");
		if(StringUtils.isEmpty(cfgPassword))
			throw new IllegalArgumentException("确认密码不能为空");
		if(!newPassword.equals(cfgPassword))
			throw new IllegalArgumentException("两次密码输入不一致");
		//2.判断原密码是否正确
		SysUser user = ShiroUtli.getUser();
		String sourceSalt = user.getSalt();//获取数据库中登陆用户的密码盐值
		SimpleHash sh = new SimpleHash("MD5", sourcePassword, sourceSalt, 1);
		if(!user.getPassword().equals(sh.toHex()))
			throw new IllegalArgumentException("原密码错误");
		//3.修改密码
		String newSalt = UUID.randomUUID().toString();
		sh = new SimpleHash("MD5", newPassword, newSalt, 1);
		int rows = sysUserDao.updatePassword(sh.toHex(), newSalt, user.getId());
		return rows;
	}

}
